﻿//*** Guidance generated code ***//
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Linq;
using System.Web.Caching;
using System.Xml.Linq;

using ProjectBase.Core;

namespace $safeprojectname$.Xml
{
    /// <summary>
    ///     Base DAO for entities stored in XML files.
    ///     Provides the basic simple property-attribute mapping for any entity.
    ///     Complex mapping must be implemented in derived class.
    /// </summary>
    /// <typeparam name = "T"></typeparam>
    /// <typeparam name = "TID"></typeparam>
    /// <typeparam name = "TFilter"></typeparam>
    public abstract class ReadXmlDao<T, TID, TFilter> : XmlDao, IReadDao<T, TID, TFilter>
        where T : class, IPersistentObjectWithTypedId<TID>, ISetID<TID>
        where TFilter : class, IFilter<TID>
    {
        #region members

        protected const string DataExtension = ".xml";
        protected const string ListKey = "|listKey";
        protected readonly string ElementName = typeof(T).Name;

        #endregion members

        public abstract IEnumerable<T> Find(TFilter filter);

        /// <summary>
        ///     Mapping from XElement to entity 'T'
        /// </summary>
        /// <param name = "element"></param>
        /// <returns></returns>
        protected abstract T CreateEntity(XElement element);

        /// <summary>
        ///     Provides basic mapping from the XElement attributes
        ///     into the entity properties, using the Property.Name
        ///     (and expecting the same for XElement.Attribute(name)).
        ///     Only ValueType and string.
        /// </summary>
        /// <param name = "entity">instance to be filled</param>
        /// <param name = "element">Attributes containing element</param>
        /// <returns>the 'entity' instance filled with the elements attributes</returns>
        protected virtual T FillEntity(T entity, XElement element)
        {
            Contract.Requires(entity.Is(), " Parameter 'entity' cannot be null, when FillEntity is called. ");
            Contract.Requires(element.Is(), " Parameter 'element' cannot be null, when FillEntity is called. ");

            foreach (PropertyDescriptor pd in TypeDescriptor.GetProperties(entity.GetType()))
            {
                var attr = element.Attribute(pd.Name);
                if (attr.IsNull())
                {
                    continue;
                }
                if (pd.PropertyType.IsValueType) // Value Types
                {
                    ValueType result;
                    if (attr.Value.TryParse(CultureInfo.InvariantCulture, pd.PropertyType, out result))
                    {
                        pd.SetValue(entity, result);
                    }
                }
                else if (pd.PropertyType.Name.Equals("String")) // String
                {
                    pd.SetValue(entity, attr.Value);
                }
            }
            return entity;
        }

        #region IDao implemented GET

        /// <summary>
        ///     Basic read operation for getting known entity
        /// </summary>
        /// <param name = "id">Entity unique int identifier</param>
        /// <returns>Entity instance or null</returns>
        public virtual T GetById(TID id)
        {
            return TheList.Where(entity => entity.ID.Equals(id)).SingleOrDefault();
        }

        /// <summary>
        ///     Basic read operation for filter-List.
        ///     Overriden implementations can use more specific filter
        /// </summary>
        /// <param name = "filter">basic filter parameters cluster, could be extended for Entity purposes and casted</param>
        /// <returns>Paged list of entities matching filter criteria</returns>
        protected virtual IList<T> GetListByCriteria(TFilter filter)
        {
            IEnumerable<T> list = new List<T>(TheList);
            foreach (var criteria in Criteria)
            {
                if (criteria.Is()) // null cannot be handled
                {
                    list = list.Where(criteria);
                }
            }

            list = AddOrder(list, filter);

            // Adjust the Paging
            filter.TotalRows = list.Count();

            if (filter.SkipRows > filter.TotalRows)
            {
                filter.CurrentPage = (filter.TotalRows / filter.TakeRows) - 1;
            }

            // Execute the Paging
            list = list.Skip(filter.SkipRows)
                .Take(filter.TakeRows);
            Criteria.Clear();
            return list.ToList();
        }

        /// <summary>
        ///     Uniqueness Check - Sometimes is useful to filter for existing entities
        ///     which are different from current entity (add or update)
        ///     Appends condition entity.ID &lt;&gt; exceptID
        /// </summary>
        /// <param name = "filter">Searching parameters</param>
        /// <param name = "exceptID">The ID which should be excepted from the filtered list</param>
        /// <returns>The filtered list without entity of provided ID</returns>
        public virtual IEnumerable<T> Find(TID exceptID, TFilter filter)
        {
            Criteria.Add(entity => !entity.ID.Equals(exceptID));
            return Find(filter);
        }

        #endregion IDao implemented GET

        #region Xml DataSource

        /// <summary>
        ///     Collection of Criteria, which can be extended in the
        ///     "IList&lt;T&gt; Find(IFilter filter);"
        ///     and is evaluated in the GetListByCriteria((IFilter filter);
        /// </summary>
        protected IList<Func<T, bool>> Criteria = new List<Func<T, bool>>();

        /// <summary>
        ///     The LIST of all entities stored in xml file.
        ///     This property can be filtered and ordered in other operations
        /// </summary>
        protected virtual IEnumerable<T> TheList
        {
            get
            {
                var list = this.Cache()[DataSourcePath + ListKey] as IEnumerable<T>;
                if (list.IsNull())
                {
                    list = DataSource.Root
                            .Elements(ElementName)
                            .Select(CreateEntity);
                    using (var cacheDependency = new CacheDependency(DataSourcePath))
                    {
                        this.Cache().Insert(DataSourcePath + ListKey, list, cacheDependency);
                    }
                }
                return list;
            }
        }

        protected virtual string DataSourceName
        {
            get { return ElementName + DataExtension; }
        }

        /// <summary>
        ///     The full path to the DataSource underlying file
        /// </summary>
        protected override string DataSourcePath
        {
            get { return Global.CreateAppDataPath(DataSourceName); }
        }

        /// <summary>
        ///     Returns the XElement; Wwith presumption that there is any with attribute 'id'
        ///     AND that the Element name Equals to 'ElementName'
        /// </summary>
        /// <param name = "id"></param>
        /// <returns></returns>
        protected virtual XElement GetElementById(TID id)
        {
            return DataSource.Root.Elements(ElementName)
                    .SingleOrDefault(e =>
                        e.Attribute(Constants.Common.ID).Value.IsEqual(id.ToString()))
                    ;
        }

        /// <summary>
        ///     Provides basic Ordering based on the property name.
        ///     If any smarter needed - do override.
        /// </summary>
        /// <param name = "list"></param>
        /// <param name = "filter"></param>
        protected virtual IEnumerable<T> AddOrder(IEnumerable<T> list, TFilter filter)
        {
            if (filter.OrderBy.IsEmpty())
            {
                return list;
            }

            var keys = filter.OrderBy.Keys.ToList();
            for (int i = keys.Count; i > 0; i--)
            {
                var key = keys[i - 1];
                var value = filter.OrderBy[key];

                var unsortable = list.Where(entity => entity.BuildMember(key).IsNull());
                var sortable = list.Where(entity => entity.BuildMember(key).Is());

                if (value)
                {
                    sortable = sortable
                        .OrderBy(entity => entity.BuildMember(key));
                }
                else
                {
                    sortable = sortable
                        .OrderByDescending(entity => entity.BuildMember(key));
                }
                list = sortable.Union(unsortable, null);
            }
            return list;
        }

        #endregion Xml DataSource
    }
}